#include "stdafx.h"
#include "ZOdbcConnection.h"
#include "ZOdbcRecordset.h"
#include "ZOdbcCommand.h"
#include "ZFrameAllocator.h"
#include "ZUtility.h"
#include <assert.h>

namespace	Z	{

namespace	Odbc	{

//------------------------------------------------------------------------------------------------------------------------
Connection::Provider::Provider(const std::string& kServerName, const std::string& kUserName, const std::string& kAuthentication)
:	serverName(kServerName),
	userName(kUserName),
	authentication(kAuthentication)
{
}

//------------------------------------------------------------------------------------------------------------------------
Connection::Connection(const std::string& serverName, const std::string& userName, const std::string& authentication)
:	mEnv(0),
	mDbc(0),
	mProvider(serverName, userName, authentication),
	mOpened(false),
	mTransaction(false),
	mAllocator(new Allocator)
{
	::SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &mEnv);
	::SQLSetEnvAttr(mEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);
	::SQLAllocHandle(SQL_HANDLE_DBC, mEnv, &mDbc);
	
	assert(mEnv != 0);
	assert(mDbc != 0);
}

Connection::~Connection()
{
	::SQLFreeHandle(SQL_HANDLE_DBC, mDbc);
	::SQLFreeHandle(SQL_HANDLE_ENV, mEnv);

	deletePtr(mAllocator);
}

bool Connection::open()
{
	if(mTransaction)
		return false;

	close();

	SQLRETURN retcode	= ::SQLConnect(mDbc, (SQLCHAR*)mProvider.serverName.c_str(), SQL_NTS, (SQLCHAR*)mProvider.userName.c_str(), SQL_NTS, (SQLCHAR*)mProvider.authentication.c_str(), SQL_NTS);
	mOpened	= SQL_SUCCEEDED(retcode);
	if(!mOpened)
		diagnosis(SQL_HANDLE_DBC, mDbc, mProvider.serverName.c_str());
	return mOpened;
}

void Connection::close()
{
	if(!mOpened)
		return;

	::SQLDisconnect(mDbc);
	mOpened	= false;
}

RecordsetPtr Connection::execute(Command& kCommand)
{
	return kCommand.execute();
}

RecordsetPtr Connection::execute(const char* format, ...)
{
	va_list args;
	va_start(args, format);
	Command kCommand(*this);
	kCommand.setCommandText(format, args);
	va_end(args);
	return execute(kCommand);
}

bool Connection::beginTrans()
{
	open();
	return setAutoCommitOff();
}

bool Connection::setAutoCommitOff()
{
	assert(!mTransaction);

	SQLRETURN retcode	= ::SQLSetConnectOption(mDbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);
	return mTransaction = SQL_SUCCEEDED(retcode);
}

bool Connection::commitTrans()
{
	return transact(SQL_COMMIT);
}

bool Connection::rollbackTrans()
{
	return transact(SQL_ROLLBACK);
}

bool Connection::transact(SQLUSMALLINT completionType)
{
	assert(mTransaction);

	SQLRETURN transact	= ::SQLTransact(mEnv, mDbc, completionType);
	SQLRETURN retcode	= ::SQLSetConnectOption(mDbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_ON);
	assert(retcode == SQL_SUCCESS);
	mTransaction	= false;
	return transact == SQL_SUCCESS;
}

void* Connection::allocateUnaligned(size_t size)
{
	return mAllocator->allocateUnaligned(size);
}

void* Connection::allocateAligned(size_t size, size_t alignment)
{
	return mAllocator->allocateAligned(size, alignment);
}

void Connection::resetAllocator()
{
	mAllocator->free();
}

_ZOdbcApi	bool setConnectionPooling()
{
	SQLRETURN retcode	= ::SQLSetEnvAttr(0, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER)SQL_CP_ONE_PER_DRIVER, SQL_IS_INTEGER);
	return SQL_SUCCEEDED(retcode);
}

void	diagnosis(SQLSMALLINT fHandleType, SQLHANDLE handle, const std::string& cause)
{
	const size_t cchErrorMsgMax	= 1024;
	SQLCHAR szErrorMsg[cchErrorMsgMax];
	const size_t cchStateSize	= 5;
	SQLCHAR szSqlState[cchStateSize+1];
	SQLSMALLINT cchErrorMsg;
	SQLINTEGER fNativeError;
	for(SQLSMALLINT iRecord = 1;;++iRecord)	{
		SQLRETURN retcode	= ::SQLGetDiagRec(fHandleType, handle, iRecord, szSqlState, &fNativeError, szErrorMsg, cchErrorMsgMax, &cchErrorMsg);
		if(!(SQL_SUCCEEDED(retcode) && ::strcmp((const char*)szSqlState, "00000")))
			break;
		std::string diagMsg((const char*)szSqlState);
		diagMsg	+= "\t";
		diagMsg	+= (const char*)szErrorMsg;
		diagMsg	+= "\n";
		diagMsg	+= cause;
		::OutputDebugString(diagMsg.c_str());
	}
}

}

}